// alm peer
// 为免麻烦，每个peer专用一个到服务器的socket
function PeerInfo (peerId, socket) {
  this.peerId = peerId;
  this.socket = socket;
  this.layerNo;
}

function AlmInfo(almId) {
  this.almId = almId;
  this.sender = "0";
  this.peers = new Map();  // peerId -> PeerInfo
}
function RoomInfo(roomId) {
  this.roomId = roomId;
  this.alms = new Map();    // almId -> AlmInfo
}

// 房间名单
var roomInfos = new Map();  // roomId -> RoomInfo
const MAX_CANDIDATE_NUM = 5;

function socketController(server, socketLimit) {
  /**
   * server: Node Server
   * socketLimit: # of socket.io connections to keep
   */
  this.io = require('socket.io')(server);
  // will store socket connections to Viewers
  //this.sockets = []; 
  this.socketMap = new Map()
  
  this.io.on('connection', (socket) => {
    let socket_ = socket;

    console.log('New connection:', socket_.id);

    // variable to bind socketController context in socket handlers
    // so that 'this' in socket handlers can access socket
    var url = socket.request.headers.referer;
    var splited = url.split('/');
    var roomId = splited[splited.length - 1];
    var user = '';


    // check # of clients
    // kick socket off client if full
    const checkClientNum = (err, clients) => {
      if (err) throw err;

      if (clients.length <= socketLimit) {
        // keep socket connection
        //this.sockets.push(socket);
        this.socketMap.set(socket.id, {roomId:roomId, almId:""})
        console.log(`Added socket: ${socket.id} with room: ${roomId}`);
        //console.log('Added sockets:', this.sockets.map(socket => socket.id));
      } else {
        socket.emit('full');
      }
    }
    this.io.sockets.clients(checkClientNum);

    socket.on('create', (peerId, almId) => {
      console.log(`create with socket.id: ${socket_.id}`);

      var info = this.socketMap.get(socket_.id);
      if (!info) {
        console.log(`can not find info for ${peerId} to create alm: ${almId}`);
        socket_.emit('createResp', 'fail');
        return;
      }
      console.log(`${peerId} join room: ${info.roomId}, create alm: ${almId}`);

      info.almId = almId; 
      var room = roomInfos.get(info.roomId);
      if (!room) {
        room = new RoomInfo(info.roomId);
        roomInfos.set(info.roomId, room);
      }
      var alm = new AlmInfo(info.almId);
      alm.sender = peerId;
      room.alms.set(almId, alm)

      let pi = new PeerInfo;
      pi.peerId = peerId;
      pi.socket = socket_;
      pi.layerNo = 0;
      alm.peers.set(peerId, pi);

      /// only support a lalm by now.
      socket_.emit('createResp', 'success');
    });
    
    socket.on('join', (peerId, almId) => {
      console.log(`join with socket.id: ${socket_.id}`);

      var info = this.socketMap.get(socket.id); 
      if (!info) {
        console.log(`can not find info for ${peerId} to join alm: ${almId}`);
        socket_.emit('createResp', 'fail');
        return;
      }
      info.almId = almId;
      var room = roomInfos.get(info.roomId);
      if (!room) {
        room = new RoomInfo(info.roomId);
        roomInfos.set(info.roomId, room);
      }
      var alm = room.alms.get(almId);
      if (!alm) {
        console.log(`alm with id: ${almId} not start!`);
        socket_.emit('joinResp', 'fail');
        return;
      }
      var layerNo = alm.peers.length;
           
      /// only support a lalm by now.
      /// not consider layno, only use ip address
      let peersInfos = [];
      for (let [id, info] of alm.peers) {
        if (peersInfos.length > MAX_CANDIDATE_NUM) {
          break;
        }
        peersInfos.push({peerId: id, layerNo: info.layerNo});
      }     
      socket_.emit('joinResp', 'success', layerNo, peersInfos);

      console.log(`${peerId} join room: ${info.roomId}, alm: ${info.almId}  with layerno: ${layerNo}`);
      
      let pi = new PeerInfo;
      pi.peerId = peerId;
      pi.socket = socket;
      pi.layerNo = layerNo;
      alm.peers.set(peerId, pi);
    });


    socket.on('signal', (from, to, data) => {
      console.log(`signal with socket.id: ${socket_.id}`);

      var info = this.socketMap.get(socket_.id); 
      if (!info) {
        console.log(`can not find info for socket ${socket_.id} to signal`);
        return;
      }

      var room = roomInfos.get(info.roomId);
      if (!room) {
        console.log("no room info for user: ", from)
        return;
      }
      var alm = room.alms.get(info.almId);
      if (!alm) {
        console.log("no alm info for user: ", from)
        return;
      }

      console.log('signal from ' + from + ' to ', to);
      let peer = alm.peers.get(to);
      if (!peer) {
        console.log('no peer with id: ', to);
      }            
      peer && peer.socket.emit('signal', from, data);
    });
 
    peer_quit = (socket, peerId) => {
      console.log(`quit with socket.id: ${socket.id}`);

      var info = this.socketMap.get(socket.id); 
      if (!info) {
        console.log(`can not find info for socket ${socket.id} to signal`);
        return;
      }

      var room = roomInfos.get(info.roomId);
      if (!room) {
        console.log("no room info for user: ", peerId)
        return;
      }
      var alm = room.alms.get(info.almId);
      if (!alm) {
        console.log("no alm info for user: ", peerId)
        return;
      }

      if (!peerId) {
        for (let [pId, peerInfo] of alm.peers) {
          if (peerInfo.socket == socket) {
            peerId = pId
            break; 
          }
        }    
      }
      console.log('quit, for peer: ', peerId);

      alm.peers.delete(peerId);                  
    }

    /// send by peer node when quit.
    socket.on('quit', (peerId) => { 
      console.log(`${peerId} quit with socket.id: ${socket_.id}`);
      peer_quit(socket_, peerId) 
    });

    // 这里监听 disconnect，就可以知道谁断开连接了
    socket.on('disconnect',  () => {
        console.log(`disconnect with socket.id: ${socket_.id}`);
        peer_quit(socket_);

        //this.sockets = this.sockets.filter(s => s == socket);
        this.socketMap.delete(socket_.id);
    });

  });
}

socketController.prototype.emitNewMagnet = function(magnetURI) {
  this.io.emit('magnetURI', magnetURI);
}

// Determines socket of chain to connect to
// TODO: dummy func right now
function getTargetSocket(sockets) {
  return sockets[0];
}

module.exports = socketController;